mod objstring;
mod objset;

use crate::evlauter::Env;
pub use objstring::*;
use objset::*;

use lazy_static::lazy_static;

use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;

#[derive(Debug, Clone, PartialEq)]
pub enum Object {
    Keyword(&'static str),
    Operator(&'static str),
    Void,
    Bool(bool),
    Integer(i64),
    Float(f64),
    String(ObjString),
    Symbol(ObjString),
    Lambda(Vec<ObjString>, Rc<Object>, Rc<RefCell<Env>>),
    CodeList(Vec<Rc<Object>>),
    List(Vec<Rc<Object>>),
}

impl Eq for Object {}

impl Object {
    pub fn is_number(&self) -> bool {
        match self {
            Object::Integer(_) | Object::Float(_) => true,
            _ => false,
        }
    }

    pub fn is_float(&self) -> bool {
        if let Object::Float(_) = self {
            true
        } else {
            false
        }
    }

    pub fn is_symbol(&self) -> bool {
        if let Object::Symbol(_) = self {
            true
        } else {
            false
        }
    }

    pub fn is_bool(&self) -> bool {
        if let Object::Bool(_) = self {
            true
        } else {
            false
        }
    }

    pub fn is_lambda(&self) -> bool {
        if let Object::Lambda(_, _, _) = self {
            true
        } else {
            false
        }
    }
}

impl fmt::Display for Object {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Object::Void => write!(f, "Void"),
            Object::Keyword(s) => write!(f, "{}", s),
            Object::Operator(s) => write!(f, "{}", s),
            Object::Integer(n) => write!(f, "{}", n),
            Object::Float(n) => write!(f, "{}", n),
            Object::Bool(b) => write!(f, "{}", b),
            Object::Symbol(s) => write!(f, "{}", s),
            Object::String(s) => write!(f, "{}", s),
            Object::Lambda(params, body, _env) => {
                write!(f, "Lambda(")?;
                for param in params {
                    write!(f, "{} ", param)?;
                }
                write!(f, ")")?;
                write!(f, " {}", body)?;
                Ok(())
            }
            Object::CodeList(list) => {
                write!(f, "(")?;
                for (i, obj) in (*list).iter().enumerate() {
                    if i > 0 {
                        write!(f, " ")?;
                    }
                    write!(f, "{}", obj)?;
                }
                write!(f, ")")
            }
            Object::List(list) => {
                write!(f, "(")?;
                for (i, obj) in (*list).iter().enumerate() {
                    if i > 0 {
                        write!(f, " ")?;
                    }
                    write!(f, "{}", obj)?;
                }
                write!(f, ")")
            }
        }
    }
}


lazy_static! {
    static ref KEYWORD_OBJ_SET: ObjectSet = ObjectSet::keyword_set();
    static ref OPERATOR_OBJ_SET: ObjectSet = ObjectSet::operator_set();
    pub static ref VOID_OBJ: RcObject = RcObject::new(Object::Void);
    pub static ref TRUE_OBJ: RcObject = RcObject::new(Object::Bool(true));
    pub static ref FALSE_OBJ: RcObject = RcObject::new(Object::Bool(false));
}

pub fn keyword_obj(s: &'static str) -> Option<Rc<Object>> {
    match KEYWORD_OBJ_SET.get(s) {
        None => None,
        Some(obj) => Some(obj.clone()),
    }
}

pub fn operator_obj(s: &'static str) -> Option<Rc<Object>> {
    match OPERATOR_OBJ_SET.get(s) {
        None => None,
        Some(obj) => Some(obj.clone()),
    }
}
